package com.aocore.proxy;

import com.aocore.packet.GC.GameLogonRequest;
import com.aocore.packet.GC.Invalid;
import com.aocore.proxy.compression.Huffman;
import com.aocore.packet.GCPacket;
import com.aocore.packet.GSPacket;
import com.aocore.packet.Packet;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 *
 * @author derek
 */
public final class GameProxy extends Proxy
{
    private boolean magic = true;

    class AOException extends Exception
    {
    }

    private static final int[] GSPacketSizeArray = new int[] {
     /* 0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F */
/* 0 */ 1,   8,   1,   12,  1,   1,   1,   6,   6,   11,  6,   6,   9,   13,  12,  16,
/* 1 */ 16,  8,   26,  14,  18,  11,  -1,  -1,  15,  2,   2,   3,   5,   3,   4,   6,
/* 2 */ 10,  12,  12,  13,  90,  90,  -1,  40,  103, 97,  15,  0,   8,   0,   0,   0,
/* 3 */ 0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   34,  8,
/* 4 */ 13,  0,   6,   0,   0,   13,  0,   11,  11,  0,   0,   0,   16,  17,  7,   1,
/* 5 */ 15,  14,  42,  10,  3,   0,   0,   14,  7,   26,  40,  -1,  5,   6,   38,  5,
/* 6 */ 7,   2,   7,   21,  0,   7,   7,   16,  21,  12,  12,  16,  16,  10,  1,   1,
/* 7 */ 1,   1,   1,   32,  10,  13,  6,   2,   21,  6,   13,  8,   6,   18,  5,   10,
/* 8 */ 4,   20,  29,  0,   0,   0,   0,   0,   0,   2,   6,   6,   11,  7,   10,  33,
/* 9 */ 13,  26,  6,   8,   -1,  13,  9,   1,   7,   16,  17,  7,   -1,  -1,  7,   8,
/* A */ 10,  7,   8,   24,  3,   8,   -1,  7,   -1,  7,   -1,  7,   -1,  0,   -1,  0,
/* B */ 1
    };

    private static final int[] GCPacketSizeArray = new int[] {
     /* 0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F */
/* 0 */ 0,   5,   9,   5,   9,   5,   9,   9,   5,   9,   9,   1,   5,   9,   9,   5,
/* 1 */ 9,   9,   1,   9,   -1,  -1,  13,  5,   17,  5,   9,   9,   3,   9,   9,   17,
/* 2 */ 13,  9,   5,   9,   5,   9,   13,  9,   9,   9,   9,   0,   0,   1,   3,   9,
/* 3 */ 9,   9,   17,  17,  5,   17,  9,   5,   13,  5,   3,   3,   9,   5,   5,   3,
/* 4 */ 1,   1,   1,   1,   17,  9,   13,  13,  1,   9,   0,   9,   5,   3,   0,   7,
/* 5 */ 9,   9,   5,   1,   1,   0,   0,   0,   3,   17,  0,   0,   0,   7,   6,   5,
/* 6 */ 1,   3,   5,   5,   9,   17,  -1,  0,   37,  1,   1,   1,   1,   13,  0,   1,
    };

    public GameProxy(SocketChannel socketChannel) throws IOException
    {
        super(socketChannel);
        System.out.println("NEW GAME PROXY");
        writeServer((byte)0xAF, (byte)0x01);
    }

    public void writePacket(GSPacket packet) throws IOException
    {
        writeServerPacket(packet);
    }

    public void writePacket(GCPacket packet) throws IOException
    {
        writeClientPacket(packet);
    }

    @Override
    protected List<Packet> parseClientPacket(ByteBuffer byteBuffer)
    {
        List<Packet> packets = new LinkedList<Packet>();
ffs:
        while (true)
        {
            if (byteBuffer.position() < 1)
            {
                break;
            }

            try
            {
                if (byteBuffer.get(0) > GCPacketSizeArray.length)
                {
                    throw new AOException();
                }

                int packet_length = GCPacketSizeArray[byteBuffer.get(0)];

                if (packet_length == 0)
                {
                    throw new AOException();
                }

                if (packet_length < 0)
                {
                    switch (byteBuffer.get(0))
                    {
                        case 0x14:
                        case 0x15: // 3 + 3 * NULLSTRING
                            packet_length = 3;
                            for (int i = 0; i < 3; i++)
                            {
                                int j = 0;
                                while(true)
                                {
                                    if (byteBuffer.position() < j + packet_length)
                                        break ffs;
                                    if (byteBuffer.get(j + packet_length) == 0)
                                    {
                                        packet_length += j + 1;
                                        break;
                                    }
                                    j++;
                                }
                            }
                            break;
                        case 0x66:
                            if (byteBuffer.position() < 5) break ffs;
                            packet_length = 3 + byteBuffer.getShort(1);
                            break;
                        default:
                            throw new AOException();
                    }
                }

                //System.out.println("GAME CLIENT PACKET LENGTH - " + packet_length);
                byte[] data = new byte[packet_length];
                byteBuffer.flip();
                byteBuffer.get(data);
                byteBuffer.compact();

                Packet packet = GCPacket.create(data);
                packets.add(packet);
            }
            catch (AOException e)
            {
                byte[] data = new byte[byteBuffer.position()];
                byteBuffer.flip();
                byteBuffer.get(data);
                byteBuffer.compact();

                Packet packet = new Invalid(data);
                packets.add(packet);
                return packets;
            }
        }

        return packets;
    }

    @Override
    protected List<Packet> parseServerPacket(ByteBuffer byteBuffer)
    {
        List<Packet> packets = new LinkedList<Packet>();

        while (true)
        {
            if (magic)
            {
                byte[] data = new byte[2];

                byteBuffer.flip();
                byteBuffer.get(data);
                byteBuffer.compact();

                magic = false;
                continue;
            }

            if (byteBuffer.position() < 2)
            {
                break;
            }

            int compressed_length = Huffman.getPacketSize(byteBuffer.array());

            if (compressed_length > byteBuffer.position())
            {
                break;
            }

            //System.out.println("GAME SERVER PACKET LENGTH - " + packet_length);
            byte[] data = new byte[compressed_length];

            byteBuffer.flip();
            byteBuffer.get(data);
            byteBuffer.compact();

//            System.out.println("-->Compressed - " + data.length);
//            for (int i = 0; i < data.length; i++)
//            {
//                System.out.print(String.format("(byte)0x%02X, ", data[i]));
//            }
//            System.out.println();

            byte[] decompressed = new byte[1024];
            int decompressed_length = Huffman.decompressDataContent(data, decompressed);

//            System.out.println("-->Decompressed - " + decompressed_length);
//            for (int i = 0; i < decompressed_length; i++)
//            {
//                System.out.print(String.format("0x%02X, ", decompressed[i]));
//            }
//            System.out.println();

            int position = 0;
            int packet_length = -1;

            while (position < decompressed_length)
            {
                switch (decompressed[position])
                {
                    case (byte) 0x26:
                    {
                        boolean found = false;
                        for (int i = 10; i < decompressed_length - position; i++)
                        {
                            if (decompressed[position + i] == 0)
                            {
                                if (found)
                                {
                                    packet_length = i + 1;
                                    break;
                                }
                                else
                                {
                                    found = true;
                                }
                            }
                        }
                    }
                    break;
                    case (byte) 0x5B:
                    {
                        ByteBuffer bb = ByteBuffer.wrap(decompressed);
                        bb.order(ByteOrder.LITTLE_ENDIAN);
                        packet_length = bb.getShort(position + 1);
                    }
                    break;
                    case (byte) 0x94:
                    {
                        packet_length = 6 + (decompressed[position + 1] * 3);
                    }
                    break;
                    case (byte) 0x9C:
                    case (byte) 0x9D:
                    {
                        packet_length = decompressed[position + 2];
                    }
                    break;
                    case (byte) 0xA8:
                    case (byte) 0xAA:
                    {
                        packet_length = decompressed[position + 6];
                    }
                    break;
                    case (byte) 0xAC:
                    {
                        packet_length = decompressed[position + 12];
                    }
                    break;
                    case (byte) 0xAE:
                    {
                        ByteBuffer bb = ByteBuffer.wrap(decompressed);
                        bb.order(ByteOrder.LITTLE_ENDIAN);
                        packet_length = 3 + bb.getShort(position + 1);
                    }
                    break;
                    default:
                    {
                        int i = decompressed[position] & 0xFF;
                        if (i <= GSPacketSizeArray.length)
                            packet_length = GSPacketSizeArray[i];
                        else
                        {
                            packet_length = decompressed_length - position;
                            System.out.println("--- ERROR ----");
                        }
                    }
                    break;
                }

                Packet packet = GSPacket.create(Arrays.copyOfRange(decompressed, position, position + packet_length));
                packets.add(packet);

//                System.out.print("packetid = " + String.format("0x%02X", decompressed[position]) + ", length = " + packet_length);
                position += packet_length;
//                System.out.println(", processed = " + position + "/" + decompressed_length);
            }
        }

        return packets;
    }

    @Override
    protected Packet processClientPacket(Packet packet)
    {
        if (packet instanceof GameLogonRequest)
        {
            GameLogonRequest gameLogonRequest = (GameLogonRequest) packet;
            awesomO = bots.get(Arrays.hashCode(gameLogonRequest.getHash()));
            awesomO.setGameProxy(this);

            try
            {
                connectClient(awesomO.getGameAddress());
            }
            catch (IOException e)
            {
                e.printStackTrace(System.err);
            }
        }

        return packet;
    }

    @Override
    protected Packet processServerPacket(Packet packet)
    {
        return packet;
    }
}
